__author__ = 'shawhen'

import sys
import ctypes
import time

from PyQt5.QtCore import *

from ..platform import what as m_what
from .. import eccatcher as m_eccatcher
from ..thread import uthread as m_uthread


class UProcess(QObject):
    sigFinished = pyqtSignal(int)  # exit code

    def __init__(self, parent=None):
        super(UProcess, self).__init__(parent)

        self._process_handle = None

        self._process_stdout_pipe_r = None
        self._process_stdout_pipe_w = None
        self._process_stdin_pipe_r = None
        self._process_stdin_pipe_w = None

        self._work_dir = None

        self._state = "blank"  # "blank"/"started"

        self._heartbeat_thread = None

    def start(self, command_line):
        if self._state != "blank":
            sys.stderr.write("start a started process, this will kill the old one")
            sys.stderr.flush()

            self.kill()

        if m_what.running_os["type"] == "windows":
            from ..sysapi.win32 import windows

            sa = windows.SECURITY_ATTRIBUTES()
            sa.nLength = ctypes.sizeof(windows.SECURITY_ATTRIBUTES)
            sa.lpSecurityDescriptor = windows.NULL
            sa.bInheritHandle = windows.TRUE

            self._process_stdout_pipe_r = windows.HANDLE()
            self._process_stdout_pipe_w = windows.HANDLE()
            create_child_stdout_pipe_result = windows.CreatePipe(self._process_stdout_pipe_r, self._process_stdout_pipe_w, sa, 0)
            if create_child_stdout_pipe_result is False:
                raise ValueError("create child stdout pipe failed")
            set_child_stdout_r_inherit_result = windows.SetHandleInformation(self._process_stdout_pipe_r, windows.HANDLE_FLAG_INHERIT, 0)
            if set_child_stdout_r_inherit_result is False:
                raise ValueError("set child stdout r inherit failed")

            self._process_stdin_pipe_r = windows.HANDLE()
            self._process_stdin_pipe_w = windows.HANDLE()
            create_child_stdin_pipe_result = windows.CreatePipe(self._process_stdin_pipe_r, self._process_stdin_pipe_w, sa, 0)
            if create_child_stdin_pipe_result == windows.FALSE:
                print("create child stdin pipe failed")
            set_child_stdin_w_inherit_result = windows.SetHandleInformation(self._process_stdin_pipe_w, windows.HANDLE_FLAG_INHERIT, 0)
            if set_child_stdin_w_inherit_result == windows.FALSE:
                print("set child stdin w inherit failed")

            startup_info = windows.STARTUPINFO()
            startup_info.cb = ctypes.sizeof(windows.STARTUPINFO)
            startup_info.hStdInput = self._process_stdin_pipe_r
            startup_info.hStdOutput = self._process_stdout_pipe_w
            startup_info.hStdError = self._process_stdout_pipe_w
            startup_info.lpDesktop = windows.NULL
            startup_info.dwFlags |= windows.STARTF_USESTDHANDLES
            startup_info.dwFlags |= windows.STARTF_USESHOWWINDOW
            startup_info.wShowWindow = windows.SW_HIDE

            self._process_info = windows.PROCESS_INFORMATION()
            create_java_process_result = windows.CreateProcess(None,
                                                               command_line,
                                                               None, None, True, 0, None,
                                                               self._work_dir, startup_info, self._process_info)
            self._process_handle = self._process_info.hProcess
            if create_java_process_result is False:
                raise ValueError("start process failed, ec: ", windows.GetLastError())
        else:
            raise NotImplementedError

        self._state = "started"

    def heartbeat(self, interval=1):
        def _heartbeat(interval):
            while True:
                time.sleep(interval)
                if m_what.running_os["type"] == "windows":
                    from ..sysapi.win32 import windows

                    exit_return = windows.GetExitCodeProcess(self._process_handle)
                    if exit_return[0] is True:
                        if exit_return[1] == windows.STILL_ACTIVE:
                            pass
                        else:
                            self.sigFinished.emit(exit_return[1])
                            break

        self._heartbeat_thread = m_uthread.UThread(target=_heartbeat, name="process heartbeat",
                                                   args=(interval, ),
                                                   eccatcher=m_eccatcher.g__eccatcher, daemon=True)
        self._heartbeat_thread.start()

    def native_handle(self):
        return self._process_handle

    def id(self):
        return self._process_info.dwProcessId

    def setWorkingDirectory(self, work_dir):
        self._work_dir = work_dir

    def read(self, bytes_to_read):
        """
        :return (True/False, read buf)
        """
        if self._state != "started":
            raise ValueError("read blank process")
        if m_what.running_os["type"] == "windows":
            from ..sysapi.win32 import windows

            return windows.ReadFile(self._process_stdout_pipe_r, bytes_to_read, None)
        else:
            raise NotImplementedError

    def write(self, data):
        """
        :return (True/False, bytes written)
        """
        if self._state != "started":
            raise ValueError("write blank process")
        data_bytes = None
        if isinstance(data, bytes):
            data_bytes = data
        elif isinstance(data, str):
            data_bytes = data.encode()
        if m_what.running_os["type"] == "windows":
            from ..sysapi.win32 import windows

            return windows.WriteFile(self._process_stdin_pipe_w, data_bytes, None)
        else:
            raise NotImplementedError

    def close(self):
        if self._state != "started":
            raise ValueError("close blank process")
        if m_what.running_os["type"] == "windows":
            from ..sysapi.win32 import windows

            windows.CloseHandle(self._process_stdout_pipe_r)
            windows.CloseHandle(self._process_stdout_pipe_w)
            windows.CloseHandle(self._process_stdin_pipe_r)
            windows.CloseHandle(self._process_stdin_pipe_w)
            self._process_stdout_pipe_r = None
            self._process_stdout_pipe_w = None
            self._process_stdin_pipe_r = None
            self._process_stdin_pipe_w = None
        else:
            raise NotImplementedError

        return self.kill()

    def kill(self):
        """
        :brief force the process to quit
        """
        if self._state != "started":
            raise ValueError("kill blank process")
        if m_what.running_os["type"] == "windows":
            from ..sysapi.win32 import windows

            result = windows.TerminateProcess(self._process_handle, 0)
        else:
            raise NotImplementedError

        self._state = "blank"
        return result

    def terminate(self):
        """
        :brief tell the process to quit
        """
        return
        raise NotImplementedError

    def waitForFinished(self, wait_seconds):
        """
        :return False: 进程在wait_seconds内没有退出. True: 在wait_seconds内退出了
        """
        interval = 0.2
        for i in range(0, int(wait_seconds//interval)):
            # 获取进程的退出码
            if m_what.running_os["type"] == "windows":
                from ..sysapi.win32 import windows

                exit_return = windows.GetExitCodeProcess(self._process_handle)
                if exit_return[0] is True:
                    if exit_return[1] == windows.STILL_ACTIVE:
                        pass
                    else:
                        return True
            else:
                raise NotImplementedError

            time.sleep(interval)  # 每次睡interval秒
        else:
            return False

    def waitForStarted(self, wait_seconds):
        """
        等待进程完成初始化，
        异常：
            TimeoutError, WindowsError
        """
        if m_what.running_os["type"] == "windows":
            from ..sysapi.win32 import windows
            from ..sysapi.win32.shared import winerror

            wait_result = windows.WaitForInputIdle(self._process_handle, wait_seconds*1000)
            if wait_result == 0:
                return
            elif wait_result == winerror.WAIT_TIMEOUT:
                raise TimeoutError()
            else:
                raise WindowsError(wait_result)
        else:
            raise NotImplementedError